// Copyright Gary Evans 2006-2007.

using System;
using System.Collections.Generic;
using System.Text;

using Taumuon.Jabuka.Physics.Collision;

namespace Taumuon.Jabuka.Physics.Test
{
	/// <summary>
	/// Class used for testing the CollisionManager
	/// </summary>
	public class MockSphere : Sphere
	{
		#region Object Lifetime

        /// <summary>
        /// Constructor
        /// </summary>
        public MockSphere(IEulerRigidBody eulerRigidBody)
			: this(eulerRigidBody, 1.0)
        {
        }

		/// <summary>
		/// Constructor
		/// </summary>
		public MockSphere(IEulerRigidBody eulerRigidBody,
			double radius)
			: base(eulerRigidBody, radius)
		{
		}

        #endregion Object Lifetime

        /// <summary>
        /// Assigns the sphere's properties from the sphere passed in.
        /// </summary>
        /// <param name="rhs">The sphere to copy the properties from.</param>
		public override void AssignFromSphere(Sphere rhs)
		{
			mockSpheresSetSphereCalledWith.Add(rhs as MockSphere);
			base.AssignFromSphere(rhs);
		}

        /// <summary>
        /// Implementation of the CheckForCollision method.
        /// </summary>
		public override CollisionType CheckForCollision(Sphere rhs)
		{
			objectsCollidedWith.Add(rhs);
			return checkForCollisionResult;
		}

        /// <summary>
        /// Implementation of the Clone method
        /// </summary>
		public override object Clone()
		{
			return this.cloneFactory.GetNextClone();
		}

		#region Testing Helpers

		/// <summary>
		/// Tests that CheckForCollision has been called against this instance
		///  with the given objects.
		/// </summary>
        /// <param name="objectsCheckCollidedWith"></param>
		/// <exception cref="ApplicationException">Thrown if the check fails.</exception>
		internal void ConfirmCollidedWith(MockSphere[] objectsCheckCollidedWith)
		{
			if (objectsCollidedWith.Count != objectsCheckCollidedWith.Length)
			{
				throw new ApplicationException("ConfirmCollidedWith - number of objects don't match");
			}
			foreach (object o in objectsCheckCollidedWith)
			{
				if (!(o is MockSphere))
				{
					throw new ApplicationException("ConfirmCollidedWith - CollisionManager's Sphere should all be MockSpheres when testing");
				}
				if (!objectsCollidedWith.Contains(o as MockSphere))
				{
					throw new ApplicationException("ConfirmCollidedWith - Expected object not present.");
				}
			}
		}

		/// <summary>
		/// Retrieves the sphere that SetSphere was last called with,
		///  or null if it has not been called or was called with null.
		/// </summary>
		/// <returns></returns>
		internal List<MockSphere> SpheresSetSphereCalledWith
		{
			get
			{
				return this.mockSpheresSetSphereCalledWith;
			}
		}

		/// <summary>
		/// Set the clone factory that will be used to obtain the MockSphere returned
        /// when the client calls Clone()
		///  The Clone() method doesn't actually Clone this instance, we control
		///  the object returned for observability via the choice of factory.
		/// </summary>
        internal IMockClonesFactory<MockSphere> MockSphereCloneFactory
		{
			set
			{
				cloneFactory = value;
			}
		}

		/// <summary>
		/// This sets the collision result that will be returned from CheckForCollision.
		/// </summary>
		internal virtual CollisionType CheckForCollisionResult
		{
			set
			{
				checkForCollisionResult = value;
			}
		}

		#endregion Testing Helpers

		#region Member Variables

		private CollisionType checkForCollisionResult = CollisionType.None;
		private List<MockSphere> mockSpheresSetSphereCalledWith = new List<MockSphere>();
        private IMockClonesFactory<MockSphere> cloneFactory = null;
		private List<Sphere> objectsCollidedWith = new List<Sphere>();

		#endregion Member Variables
	}

    /// <summary>
    /// Mock sphere class used for iteration tests.
    /// </summary>
    public class MockSphereIterations : MockSphere
    {
        #region Object Lifetime

        /// <summary>
        /// Constructor
        /// </summary>
        public MockSphereIterations(IEulerRigidBody eulerRigidBody, double timeOfCollision)
			: this(eulerRigidBody, 1.0, timeOfCollision)
        {
        }

		/// <summary>
		/// Constructor
		/// </summary>
        public MockSphereIterations(IEulerRigidBody eulerRigidBody,
			double radius, double timeOfCollision)
			: base(eulerRigidBody, radius)
		{
            this.timeOfCollision = timeOfCollision;
		}

        #endregion Object Lifetime

        /// <summary>
        /// Implementation of the CheckForCollision method.
        /// </summary>
        public override CollisionType CheckForCollision(Sphere rhs)
        {
            double lastUpdateTime = 
                ((MockEulerRigidBody)this.EulerRigidBody ).UpdateTimes[
                ((MockEulerRigidBody)this.EulerRigidBody ).UpdateTimes.Count-1];

            if (MathUtils.IsEqualWithinTol(lastUpdateTime,
                timeOfCollision, Tolerance))
            {
                return CollisionType.Collision;
            }
            else if (lastUpdateTime > timeOfCollision)
            {
                return CollisionType.Intersection;
            }
            return CollisionType.None;
        }

        private double timeOfCollision = 0.0;

        private const double Tolerance = 1e-10;
    }
}
